home *** CD-ROM | disk | FTP | other *** search
/ Disc to the Future 2 / Disc to the Future Part II Programmer's Reference (Wayzata Technology)(6013)(1992).bin / MAC / THINKC / 4_0 / STANDALO / SHOWINIT / INIT_ART.ICL next >
Text File  |  1990-05-29  |  9KB  |  65 lines

  1. Writing an INIT in Think C
  2.     by Eric Shapiro
  3.  
  4.  
  5.  
  6. What is an INIT?
  7. An INIT is simply code run at system startup. During boot time, the system (actually an INIT resource within the System) scans the System Folder for files with type ╘INIT╒. When a file of type ╘INIT╒ is found, the file is opened and all resources within that file of type ╘INIT╒ are loaded and executed.
  8.  
  9. INITs can be very useful, especially when patching operating system calls. Since INITs are run before MultiFinder is loaded, patching a system call in an INIT will affect all applications under MultiFinder. While INITs are powerful tools, they are more complicated to write than stand-alone programs since the normal launching/loading process is bypassed. Much of the information that follows applies to any CODE resource including CDEVs, Desk Accessories, XCMDs, and Drivers. 
  10.  
  11.  
  12. Globals, globals, wherefore art thou globals?
  13. Problem #1 with CODE resources is that they typically can╒t have global variables, static variables, or even embedded strings. The problem has to do with the fact that CODE resources often run in another application╒s memory heap. That application has its own global variables pointed to by register A5. Think C has the ability to generate code that uses register A4 as its globals pointer instead of A5. The CODE segment╒s globals are stored within the CODE segment itself, immediately following the actual machine code. As long as the combination of code and global data doesn╒t exceed 32K, this scheme works very well. When the INIT is called at startup, register A0 will contain a pointer to the INIT entry point. We have to copy this value into register A4 before we can access our globals.
  14.  
  15. Problem #2 with INITs is that when we patch traps, our trap intercept routines will be called without register A0 or A4 being setup. When the INIT was first loaded and run, register A0 contained a pointer to it. Now we╒re bypassing the operating system so register A0 could contain anything. The trick is to save the initial contents of A0 from our first call and restore that value into A4 whenever our patch is called. Where can we save A4? We can╒t put it in a global, since we can╒t get at our globals without first setting up A4 (a perfect Catch-22). We can╒t save it in a particular memory location, since there may be many patches installed by several different INITs. To save A0, we use a nifty (nasty?) little trick called self-modifying code. We use a little inline assembly language to create a 4 byte hole in our code. Since we know where the routine is relative to where we╒re executing, we can save A4 into this hole in our code and restore it whenever we need to. Think C provides several macros to do this in the file SetUpA4.h. (Note: self-modifying code often fails on machines with instruction caches. This code works because we don╒t modify a routine that is run again immediately ╨ it could possibly fail on future CPUs. I seem to remember a trap to flush the address cache ╨ this may be a future addition to the code).
  16.  
  17.  
  18. Initializing Quickdraw
  19. Problem #3 has to do with initializing the toolbox managers. All of the managers are easy to initialize except for the most important one ╨ Quickdraw (we need Quickdraw to show our icon on the screen when we╒re loading). In stand-alone Mac programs, we simply call InitGraf(&thePort) to initialize Quickdraw. Unbeknownst to many Mac programmers is that thePort is the last field in a 206 byte structure allocated for us by our development systems. Quickdraw uses this space to store its own global variables in. Since Think C allows the use of globals within CODE resources, we can just allocate a 206 byte structure in global space and pass the address of the last field (thePort) to InitGraf(). This is done as follows:
  20.     #define GrafSize        206    /* from IM I-209. Total QDraw storage. */
  21.     #define ADD_GRAFSIZE        GrafSize - 130;    /* 130 bytes in shown fields */
  22.     typedef struct
  23.     {
  24.          char        filler[ADD_GRAFSIZE];    /* internal QD storage */
  25.         long        randSeed;
  26.         BitMap        screenBits;
  27.         Cursor        arrow;
  28.         Pattern    dkGray;
  29.         Pattern    ltGray;
  30.         Pattern    gray;
  31.         Pattern    black;
  32.         Pattern    white;
  33.         GrafPtr    thePort;            /* current port */
  34.     } QD_GLOBALS;
  35.     static QD_GLOBALS our_qd;
  36.     InitGraf(&our_qd.thePort);
  37.  
  38. Be sure to use the fields of this structure instead of the globals thePort, screenBits,arrow,black, etc. Other toolbox managers can be initialized as desired. Note that InitWindows() will clear the screen to gray, erasing the other icons on the screen. When opening windows at INIT time, there are two low memory globals that must be cleared: DeskHook and DragHook.  There is no need to initialize the Macintosh Managers in Desk Accessories, XCMDs, or other CODE resources that live within an application╒s heap.
  39.  
  40.  
  41. Patching Traps
  42. Trick #3 involves patching traps. Since the toolbox uses Pascal calling conventions, we have to set up our patch routines as pascal routines. This is done in Think C by adding the pascal prefix to our routine declaration. It is imperative that the return value be correctly supplied or the stack will get munged. The procedure SysBeep(), for example, would be declared:
  43.     pascal void SysBeep(duration)
  44.         int duration;
  45. in Think C. Be aware that many calls documented in Inside Mac are not actually trap calls.  The calls labeled ╥Not in ROM╙ can not easily be patched. Note also that some Managers (such as HFS) use the same trap number to call several routines (the value in register D0 determines which call within HFS will be placed).  Also, it is somewhat dangerous to ╥tail patch╙ traps.  A ╥tail patch╙ is a patch that receives control back after the actual trap is called.  Apple╒s bug fixing mechanisms are sometimes confused by this technique.  One more thing to remember is that your patch must be careful about allocating memory.  If the original trap didn╒t move memory, your trap should not either.  Allocation calls from a patch will generally allocate memory from the current application heap - use NewPtrSys or NewHandleSys to allocate from the System Heap.
  46.  
  47.  
  48. Registers
  49. Trick #4 involves register usage. Toolbox calls must preserve the contents of certain registers. Think C code may change these registers, so we╒ll push the registers onto the stack whenever our patch is called and pop them off the stack when we╒re done. To be safe, we╒ll save and restore nearly all the registers (since future versions of Think C may use different register schemes).
  50.  
  51.  
  52. Staying in memory
  53. How does our INIT stay in memory permanently? We╒ll set the System Heap bit on our INIT resource so it gets loaded into the System Heap. We╒ll then call DetachResource() on ourselves so that our memory won╒t get released when the INIT file is closed. Inside Mac V describes two other ways to allocate permanent storage. We can dynamically allocate blocks of memory in the System Heap by setting a particular bit on the allocation calls (see the Tech Note that describes NewSysPtr() or the code that follows this article).  To make sure the System Heap is expanded enough for our memory needs, we add a ╘sysz╒ resource with id=0 to the INIT file.  The first 4 bytes of this resource should contain the amount of System Heap space needed by the INIT (this value can be editted using ResEdit╒s general byte editor).
  54.  
  55.  
  56. Putting up the ICON
  57. How are the INIT icons put on the screen? There are two tricks to plotting the INIT icons. Trick number one is figuring out where (horizontally) to put the icon. Trick number two is how to draw an ICN# on the desktop without leaving a white rectangle around the interior image. In order to figure out where to put the INIT icon at boot time, we require the cooperation of other INITs. Luckily, a simple solution was published that nearly all INITs use. The solution is to use a particular low memory location to figure out where to place the icon. Once the icon has been placed on the screen, this location is incremented so the next icon will appear to its right. We checksum the location to see if we╒re the first INIT to run. Plotting the icon isn╒t too difficult. It involves punching a white hole in the desktop by using CopyBits() in srcBic (bit-clear mode) with the icon╒s mask. We then use CopyBits() with the icon in srcOr mode.
  58.  
  59.  
  60. Think C
  61. Under Think C╒s ╥Set Project Type╙ menu item, select CODE resource and type in ╘INIT╒ for the resource type. Any resource id should be ok, but numbers greater than 256 are preferred for non-system resources. The System Heap bit can also be set at this point.
  62.  
  63.  
  64. The code that follows
  65. The code for a simple INIT follows. It simply patches SysBeep() and instead calls the original SysBeep() twice. This will show how to pass calls through to the original toolbox calls when necessary. Debugging INITs can be tedious, so the compile-time #define APPLICATION can be used to toggle between creating an application and an INIT. I make no guarantees on the code, but it appears to work ok. Let me know if you find any bugs.  -Eric